#include "NezhaEngine.h"
#include "SubsystemAdaptor.h"
#include "Renderer.h"
#include "TestSystem.h"
#include "DefaultSceneGraph.h"
#include "NezhaCFileSystem.h"

namespace Nezha
{
	NEZHA_SINGLETON_IMPLEMENT(NezhaEngine)

	NezhaEngine::NezhaEngine()
	:mRunning(false)
	,mSystemEventQueue(NULL)
	,mSceneGraph(NULL)
	,mFrameDelta(0)
	,mLastFrameTime(0)
	,mDefFileSystem(NULL)
	{
#ifdef NEZHA_PROFILER_SUPPORT
		msProfileSystem = NULL;
#endif

		memset(mSubsys, 0, sizeof(mSubsys));
		memset(mPlugins, 0, sizeof(mPlugins));
	}

	NezhaEngine::~NezhaEngine()
	{

	}

	bool NezhaEngine::initialize(const EngineConfig& cfg, const PArray<SubsystemAdaptor*>& adaptors)
	{
		// 
		mEngineConfig = cfg;

//#ifdef NZOS_WIN32
//		mSystemEventQueue = CreateDefaultSystemEventQueue();
//#endif //end NZOS_WIN32

        mSystemEventQueue = CreateDefaultSystemEventQueue();
        
#ifdef NEZHA_PROFILER_SUPPORT

		msProfileSystem = NZ_New ProfileSystem();

#endif

		for(PArray<SubsystemAdaptor*>::const_iterator it = adaptors.begin(); it != adaptors.end(); it++)
		{
			if((*it))
			{
				if(mSubsys[(*it)->getType()])
				{
					NZError("Dumplicated plugin type %d, ignore.", (*it)->getType());
				}

				mSubsys[(*it)->getType()] = (*it);
			}
		}

		for(StringArray::iterator it = mEngineConfig.mInitialPluginNames.begin(); it != mEngineConfig.mInitialPluginNames.end(); it++)
		{
			DynamicLibary* dll = NZ_New DynamicLibary();

			if(dll->load((*it)))
			{
				NZ_Plugin_Create func = (NZ_Plugin_Create)dll->getProcAddress(SubsystemAdaptor::msCreateEntryName.c_str());

				if(func)
				{
					SubsystemAdaptor* sys = func();

					if(mSubsys[sys->getType()])
					{
						NZError("Dumplicated plugin type %d, ignore and destroy current.", sys->getType());

						NZ_Plugin_Destroy func_des = (NZ_Plugin_Destroy)dll->getProcAddress(SubsystemAdaptor::msDestroyEntryName.c_str());
						if(func_des)
						{
							func_des();
						}
						else
						{
							NZError("Invalid plugin implemention, can't find destroy func %s", SubsystemAdaptor::msDestroyEntryName.c_str());
						}

						NZ_Delete dll;
						continue;
					}

					mSubsys[sys->getType()] = sys;
					mPlugins[sys->getType()] = dll;
					continue;
				}
				else
				{
					NZError("Can't get process %s from %s", SubsystemAdaptor::msCreateEntryName.c_str(), dll->getName().c_str());
				}
			}
			else
			{
				NZError("Failed to load plugin %s", dll->getName().c_str());
			}

			NZ_Delete dll;
		}

		if(!stream::IFileSystem::GetInterface())
		{
			// no custom file system setup yet, create default version.

			mDefFileSystem = NZ_New stream::CFileSystem();
			stream::IFileSystem::SetGlobalFileSystem(mDefFileSystem);
		}

		for(u32 i = 0; i < SYS_NUM; i++)
		{
			if(mSubsys[i] && !mSubsys[i]->initialize(this))
			{
				uninitialize();

				return false;
			}
		}

		// TODO

		if(mEngineConfig.mAttachTestSystem)
		{
			NZ_New TestSystem();
		}

		// use default scene graph for now.
		mSceneGraph = NZ_New DefaultSceneGraph();
		mSceneGraph->initialize();
		Camera* mainCam = mSceneGraph->createCamera("MainCamera", true);

		mRenderer = Renderer::GetGlobal();
		mRenderer->resetSceneGraph(mSceneGraph);
		mRenderer->setCamera(mainCam);

		mRunning = true;
		mFrameDelta = mLastFrameTime = 0;

		return true;
	}

	const char* NezhaEngine::getSubsystemModuleName(Subsystems ty) const
	{
		if(mPlugins[ty])
		{
			return mPlugins[ty]->getName().c_str();
		}

		return NULL;
	}

	NZ_NativeWindow NezhaEngine::_createNativeWindow(const EngineConfig& cfg)
	{
		return InternalCreateNativeWindow(cfg);
	}

	void NezhaEngine::_destroyNativeWindow()
	{
		// TODO
	}

	bool NezhaEngine::update()
	{
		if(!mRunning)
		{
			return false;
		}

		float dt = 0;

		if(mLastFrameTime == 0)
		{
			mLastFrameTime = GetTimeSeconds();
			mFrameDelta = 0.001f;
		}
		else
		{
			float curTime = GetTimeSeconds();
			mFrameDelta = curTime - mLastFrameTime;
			mLastFrameTime = curTime;
		}

		// TODO if we have a global time scale.
		dt = mFrameDelta;

		if(mSystemEventQueue)
		{
			mSystemEventQueue->update();
		}

		if(mEngineConfig.mAttachTestSystem)
		{
			TestSystem::getSingletonPtr()->update(dt);
		}

		if(mSceneGraph)
		{
			mSceneGraph->update(dt);
		}

#ifdef NEZHA_PROFILER_SUPPORT
		msProfileSystem->startFrame();
#endif

		// TODO caculate frame delta

		// TODO update all these subsystem

		if(mRenderer)
		{
			if(mRenderer->beginFrame())
			{
				mRenderer->drawScene();

				mRenderer->endFrame();
			}

			mRenderer->update(dt);
		}

#ifdef NEZHA_PROFILER_SUPPORT
		msProfileSystem->endFrame();
#endif

		return mRunning;
	}

	void NezhaEngine::uninitialize()
	{
		if(mSceneGraph)
		{
			mRenderer->resetSceneGraph(NULL);
			NEZHA_DELETE(mSceneGraph);
		}

		if(TestSystem::getSingletonPtr())
		{
			TestSystem* singleton = TestSystem::getSingletonPtr();
			NZ_Delete singleton;
		}

		for(int i = SYS_NUM - 1; i >= 0; i--)
		{
			if(mSubsys[i])
			{
				mSubsys[i]->uninitialize();
			}
		}

		for(u32 i = 0; i < SYS_NUM; i++)
		{
			if(mPlugins[i])
			{
				mPlugins[i]->unload();
			}
		}

#ifdef NEZHA_PROFILER_SUPPORT

		NEZHA_DELETE(msProfileSystem);

#endif

		if(mDefFileSystem)
		{
			if(stream::IFileSystem::GetInterface() == mDefFileSystem)
			{
				stream::IFileSystem::SetGlobalFileSystem(NULL);
			}

			NEZHA_DELETE(mDefFileSystem);
		}

		NEZHA_DELETE(mSystemEventQueue);
	}

	UserDataPtr NezhaEngine::getPlatformVar(const _string& name)
	{
		return getUserData(name);
	}

	void NezhaEngine::notifySystemQuitEvent()
	{
		mRunning = false;
	}

	void NezhaEngine::addUserData(const _string& name, UserDataPtr data)
	{
		mUserDatas[name] = data;
	}

	UserDataPtr NezhaEngine::getUserData(const _string& name)
	{
		UserDataMap::iterator it = mUserDatas.find(name);
		if(it != mUserDatas.end())
		{
			return it->second;
		}

		return (UserDataPtr)NULL;
	}

	UserDataPtr NezhaEngine::removeUserData(const _string& name)
	{
		UserDataMap::iterator it = mUserDatas.find(name);
		if(it != mUserDatas.end())
		{
			UserDataPtr ud = it->second;
			mUserDatas.erase(it);

			return ud;
		}

		return (UserDataPtr)NULL;
	}

	UserDataPtr NezhaEngine::_getInternalInterface(const _string& name)
	{
		if(name == "_NezhaEngine_Internal_Config")
		{
			return (UserDataPtr)&mEngineConfig;
		}

		return (UserDataPtr)NULL;
	}

}